package com.mdb.data;

import com.mdb.config.MongoDBConfig;
import com.mdb.config.IDBConfig;
import com.mdb.log.LogAdapter;
import com.mdb.util.JsonUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.bson.types.ObjectId;

/**
 * 操作Mongodb数据
 * @author Zheng.Ma
 *
 */
public class MgDataHandler implements IDataHandler{

	@SuppressWarnings("unchecked")
	@Override
	public <T extends BaseModel> T get(ITable table, Condition[] conditions) {
		DBObject o = findOne(table.getName(), createQuery(conditions));
		return (T) (o == null ? null : table.getCreator().create((BasicDBObject) o));
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends BaseModel> List<T> getList(ITable table, Condition[] conditions) {
		DBObject query = createQuery(conditions);
		List<DBObject> list = null;
		if (query == null)
			list = find(table.getName());
		else {
			list = find(table.getName(), query);
		}
		List<BaseModel> models = new ArrayList<BaseModel>(list.size());
		for (int i = 0; i < list.size(); i++) {
			models.add(table.getCreator().create((BasicDBObject) list.get(i)));
		}
		return (List<T>) models;
	}
	
	@Override
	public <T extends BaseModel> T getById(ITable table, String id) {
		return get(table, new Condition[] { new Condition("_id", new ObjectId(id)) });
	}

	@Override
	public <T extends BaseModel> T insert(T model) {
		BasicDBObject o = model.getAndResetPo();
		insert(model.getTable().getName(), new DBObject[] { o });
		model.setId(o.getObjectId("_id"));
		return model;
	}

	@Override
	public void update(BaseModel model) {
		if (model.hasChanged()) {
			BasicDBObject po = model.getAndResetPo();
			update(model.getTable().getName(), model.getId(), po);
		}
	}

	@Override
	public void delete(BaseModel model) {
		delete(model.getTable().getName(), model.getId());
	}

	@Override
	public void delete(ITable table, Condition[] conditions) {
		DBObject query = createQuery(conditions);
		debug("delete", table.getName(), new DBObject[] { query });
		MongoException expt = getCollection(table.getName()).remove(query).getLastError().getException();
		if (expt != null)
			throw expt;
	}

	@Override
	public void insert(String tableName, Object[] arr) {
		debug("insert", tableName, (DBObject[])arr);
		MongoException expt = getCollection(tableName).insert((DBObject[])arr).getLastError().getException();
		if (expt != null)
			throw expt;
	}

	@Override
	public void delete(String tableName, Object id) {
		debug1("delete", tableName, id);
		DBObject _id = new BasicDBObject("_id", id);
		MongoException expt = getCollection(tableName).remove(_id).getLastError().getException();
		if (expt != null)
			throw expt;
	}
	
	@Override
	public void delete(String tableName, Object[] ids) {
		BasicDBObject deleteObj = new BasicDBObject("_id", new BasicDBObject("$in", ids));
		debug("delete", tableName, new DBObject[] { deleteObj });
		MongoException expt = getCollection(tableName).remove(deleteObj).getLastError().getException();
		if (expt != null)
			throw expt;
	}
	
	@Override
	public void update(String tableName, Object id, Object o) {
		debug2("update", tableName, (ObjectId)id, (DBObject)o);
		DBObject _id = new BasicDBObject("_id", id);
		DBObject update = new BasicDBObject("$set", o);
		MongoException expt = getCollection(tableName).update(_id, update).getLastError().getException();
		if (expt != null)
			throw expt;
	}

	@Override
	public long count(String tableName, Object queryObj) {
		debug("count", tableName, new DBObject[] { (DBObject) queryObj });
		return getCollection(tableName).getCount((DBObject) queryObj);
	}
	
	private DBCollection getCollection(String tableName) {
		IDBConfig iDbConfig = MongoDBConfig.getDBConfig();
		return ((DB)iDbConfig.getDB()).getCollection(tableName);
	}

	private DBObject findOne(String tableName, DBObject queryObj) {
		debug("findOne", tableName, new DBObject[] { queryObj });
		return getCollection(tableName).findOne(queryObj);
	}

	private List<DBObject> find(String tableName) {
		return _find(tableName).toArray();
	}

	private List<DBObject> find(String tableName, DBObject queryObj) {
		return _find(tableName, queryObj).toArray();
	}

	private DBCursor _find(String tableName) {
		debug("find", tableName, new DBObject[0]);
		return getCollection(tableName).find();
	}

	private DBCursor _find(String tableName, DBObject ref) {
		debug("find", tableName, new DBObject[] { ref });
		return getCollection(tableName).find(ref);
	}

	private void debug(String desc, String tableName, DBObject[] refs) {
		if (LogAdapter.DB_LOG.isDebugEnabled(tableName))
			LogAdapter.DB_LOG.debug("{}: {} {}", new Object[] { desc, tableName, toJson(refs) });
	}

	private void debug1(String desc, String tableName, Object id) {
		if (LogAdapter.DB_LOG.isDebugEnabled(tableName))
			LogAdapter.DB_LOG.debug("{}: {} id={}", new Object[] { desc, tableName, id });
	}

	private void debug2(String desc, String tableName, Object id, DBObject ref) {
		if (LogAdapter.DB_LOG.isDebugEnabled(tableName))
			LogAdapter.DB_LOG.debug("{}: {} id={}, {}", new Object[] { desc, tableName, id, toJson(new DBObject[] { ref }) });
	}
	
	@SuppressWarnings("unchecked")
	private String toJson(DBObject[] os) {
		if (os != null) {
			@SuppressWarnings("rawtypes")
			Map[] objs = new Map[os.length];
			for (int i = 0; i < os.length; i++) {
				objs[i] = os[i].toMap();
				Object id = objs[i].get("_id");
				if (id != null) {
					objs[i].put("_id", id.toString());
				}
			}
			Object o = objs.length == 1 ? objs[0] : objs;
			return JsonUtils.serialize(o);
		}

		return null;
	}
	
	private final DBObject createQuery(Condition[] conditions) {
		if ((conditions == null) || (conditions.length <= 0)) {
			return null;
		}
		DBObject query = new BasicDBObject();
		for (int i = 0; i < conditions.length; i++) {
			Condition condition = conditions[i];
			if (condition.op.key == null) {
				query.put(condition.name, condition.value);
			} else {
				DBObject obj = (DBObject) query.get(condition.name);
				if (obj != null)
					obj.put(condition.op.key, condition.value);
				else
					query.put(condition.name, new BasicDBObject(condition.op.key, condition.value));
			}
		}
		return query;
	}
}